/*
 *  BOEntity.cpp
 *  EidoTron
 *
 *  Created by Luca Di Franco on 09/03/11.
 *  Copyright 2011 Luca Di Franco. All rights reserved.
 *
 */

#include "BOEntity.h"

#include <SDL_opengl.h>
#include <stdio.h>
#include <float.h>
#include <iostream>
#include <fstream>
#include <algorithm>

#include "Material.h"
#include "../Utilities/Path.h"
#include "../Utilities/StringExtension.h"

using std::cout;
using std::fstream;
using std::pair;
using std::min;
using std::max;

BOEntity::BOEntity(const char* fileName)
{
	fstream* fileStream = new fstream(fileName, fstream::in);
	if (fileStream->fail())
	{
		cout << "File " << fileName << " not found!\nSYSTEM WILL EXIT";
		exit(EXIT_FAILURE);
	}
	vector<Vertex> vertexList;
	vector<Normal> normalList;
	vector<TextCoord> textCoordList;
	Material* currMaterial = NULL;
	char* line = new char[256];
	while (!fileStream->eof())
	{
		fileStream->getline(line, 256);
		if (strncmp(line, "v ", 2) == SAME_STRING)
		{
			Vertex tempVertex;
			sscanf(line, "v %f %f %f", &tempVertex.x, &tempVertex.y, &tempVertex.z);
			vertexList.push_back(tempVertex);
		}
		else if (strncmp(line, "vn ", 3) == SAME_STRING)
		{
			Normal tempNormal;
			sscanf(line, "vn %f %f %f", &tempNormal.x, &tempNormal.y, &tempNormal.z);
			normalList.push_back(tempNormal);
		}
		else if (strncmp(line, "vt ", 3) == SAME_STRING)
		{
			TextCoord tempTextCoord;
			sscanf(line, "vt %f %f", &tempTextCoord.s, &tempTextCoord.t);
			textCoordList.push_back(tempTextCoord);
		}
		else if (strncmp(line, "f ", 2) == SAME_STRING)
		{
			int vi[3], ni[3], ti[3];
			int readed = sscanf(line, "f %i//%i %i//%i %i//%i", &vi[0], &ni[0], &vi[1], &ni[1], &vi[2], &ni[2]);
			if (readed < 6)
				readed = sscanf(line, "f %i/%i/%i %i/%i/%i %i/%i/%i", &vi[0], &ti[0], &ni[0], &vi[1], &ti[1], &ni[1], &vi[2], &ti[2], &ni[2]);			
			map< Material*, vector< Vertex >* >::iterator checkIter = verticesMap.find(currMaterial);
			if (checkIter == verticesMap.end())
			{
				verticesMap.insert(pair< Material*, vector< Vertex >* >(currMaterial, new vector< Vertex >));
				normalsMap.insert(pair< Material*, vector< Normal >* >(currMaterial, new vector< Normal >));
				if (readed > 6)
					textCoordsMap.insert(pair< Material*, vector< TextCoord >* >(currMaterial, new vector< TextCoord >));
			}
			map< Material*, vector< Vertex >* >::iterator verticesIter = verticesMap.find(currMaterial);
			map< Material*, vector< Normal >* >::iterator normalsIter = normalsMap.find(currMaterial);
			map< Material*, vector< TextCoord >* >::iterator textCoordsIter = textCoordsMap.find(currMaterial);
			for (int i = 0; i < 3; i++)
			{
				verticesIter->second->push_back(vertexList.at(vi[i]-1));
				normalsIter->second->push_back(normalList.at(ni[i]-1));
				if (textCoordsIter != textCoordsMap.end())
					textCoordsIter->second->push_back(textCoordList.at(ti[i]-1));
			}
		}
		else if (strncmp(line, "mtllib ", 7) == SAME_STRING)
		{
			char mtlFileName[128];
			sscanf(line, "mtllib %s", mtlFileName);
			vector< Material* >* newMaterials = Material::materialsFromFile(mtlFileName);
			materials.insert(materials.end(), newMaterials->begin(), newMaterials->end());
			delete newMaterials;
		}
		else if (strncmp(line, "usemtl ", 7) == SAME_STRING)
		{
			char materialName[128];
			sscanf(line, "usemtl %s", materialName);
			currMaterial = Material::find(&materials, materialName);
		}
	}
	fileStream->close();
	delete fileStream;
}

BOEntity::~BOEntity()
{
	for (vector< Material* >::iterator iter = materials.begin(); iter != materials.end(); iter++)
		delete *iter;
}

void BOEntity::generateBufferObjects()
{
	for (vector< Material* >::iterator iter = materials.begin(); iter != materials.end(); iter++)
	{
		Material* currMaterial = *iter;
		map< Material*, vector< Vertex >* >::iterator verticesIter = verticesMap.find(currMaterial);
		if (verticesIter != verticesMap.end())
		{			
			unsigned int vbo, nbo, tbo;
			vector< Vertex >* vertices = verticesIter->second;
			float* verticesArray = (float*) &(*vertices)[0];			
			glGenBuffers(1, &vbo);
			glBindBuffer(GL_ARRAY_BUFFER, vbo);
			glBufferData(GL_ARRAY_BUFFER, 3 * vertices->size() * sizeof(float), verticesArray, GL_STATIC_DRAW);
			verticesBuffersMap.insert(pair< Material*, unsigned int >(currMaterial, vbo));
			vector< Normal >* normals = normalsMap.find(currMaterial)->second;
			float* normalsArray = (float*) &(*normals)[0];
			glGenBuffers(1, &nbo);
			glBindBuffer(GL_ARRAY_BUFFER, nbo);
			glBufferData(GL_ARRAY_BUFFER, 3 * normals->size() * sizeof(float), normalsArray, GL_STATIC_DRAW);
			normalsBuffersMap.insert(pair< Material*, unsigned int >(currMaterial, nbo));
			map< Material*, vector< TextCoord >* >::iterator textCoordsIter = textCoordsMap.find(currMaterial);
			if (textCoordsIter != textCoordsMap.end())
			{
				vector< TextCoord >* textCoords = textCoordsIter->second;
				float* textCoordsArray = (float*) &(*textCoords)[0];
				glGenBuffers(1, &tbo);
				glBindBuffer(GL_ARRAY_BUFFER, tbo);
				glBufferData(GL_ARRAY_BUFFER, 2 * textCoords->size() * sizeof(float), textCoordsArray, GL_STATIC_DRAW);
				textCoordsBufferMap.insert(pair< Material*, unsigned int >(currMaterial, tbo));
			}
		}
	}
}

void BOEntity::deleteBufferObjects()
{
	map< Material*, unsigned int >::iterator iter;
	for (iter = verticesBuffersMap.begin(); iter != verticesBuffersMap.end(); iter++)
		glDeleteBuffers(1, &(iter->second));
	verticesBuffersMap.clear();
	for (iter = normalsBuffersMap.begin(); iter != normalsBuffersMap.end(); iter++)
		glDeleteBuffers(1, &(iter->second));
	normalsBuffersMap.clear();
	for (iter = textCoordsBufferMap.begin(); iter != textCoordsBufferMap.end(); iter++)
		glDeleteBuffers(1, &(iter->second));
	textCoordsBufferMap.clear();
}

void BOEntity::renderEntity()
{
	glEnable(GL_LIGHTING);
	for (vector< Material* >::iterator iter = materials.begin(); iter != materials.end(); iter++)
	{
		Material* currMaterial = *iter;
		map< Material*, unsigned int >::iterator verticesBuffersMapIter = verticesBuffersMap.find(currMaterial);
		map< Material*, unsigned int >::iterator normalsBuffersMapIter = normalsBuffersMap.find(currMaterial);
		map< Material*, unsigned int >::iterator textCoordsBufferMapIter = textCoordsBufferMap.find(currMaterial);
		if (verticesBuffersMapIter != verticesBuffersMap.end())
		{
			currMaterial->startUse();
			glBindBuffer(GL_ARRAY_BUFFER, verticesBuffersMapIter->second);
			glVertexPointer(3, GL_FLOAT, 0, NULL);
			glEnableClientState(GL_VERTEX_ARRAY);
			glBindBuffer(GL_ARRAY_BUFFER, normalsBuffersMapIter->second);
			glNormalPointer(GL_FLOAT, 0, NULL);
			glEnableClientState(GL_NORMAL_ARRAY);
			if (textCoordsBufferMapIter != textCoordsBufferMap.end())
			{
				glBindBuffer(GL_ARRAY_BUFFER, textCoordsBufferMapIter->second);
				glTexCoordPointer(2, GL_FLOAT, 0, NULL);
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			}
			int nVertices = verticesMap[currMaterial]->size();
			glDrawArrays(GL_TRIANGLES, 0, nVertices);
			glDisableClientState(GL_VERTEX_ARRAY);
			glDisableClientState(GL_NORMAL_ARRAY);
			if (textCoordsBufferMapIter != textCoordsBufferMap.end())
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			currMaterial->stopUse();
		}
	}
	glDisable(GL_LIGHTING);
}

float* BOEntity::minMaxVal()
{
	float* minMax = new float[6];
	minMax[0] = minMax[1] = minMax[2] = FLT_MAX;
	minMax[3] = minMax[4] = minMax[5] = -FLT_MAX;
	for (vector< Material* >::iterator iter = materials.begin(); iter != materials.end(); iter++)
	{
		Material* currMaterial = *iter;
		map< Material*, vector< Vertex >* >::iterator verticesIter = verticesMap.find(currMaterial);
		if (verticesIter != verticesMap.end())
		{
			float* verticesArray = (float*) &(*verticesIter->second)[0];
			for (int i = 0; i < verticesIter->second->size(); i++)
			{
				minMax[i%3] = min(minMax[i%3], verticesArray[i]);
				minMax[(i%3)+3] = max(minMax[(i%3)+3], verticesArray[i]);
			}
		}
	}
	return minMax;
}